#include "nosal/log/internal/log_manager.h"
#include "nosal/log/internal/utility.h"
#include "nosal/log/logger.h"
#include "slog2/slog2.h"
#include <tuple>
#include <iostream>

namespace netaos {
namespace nosal {
namespace log {
namespace internal {

namespace {

const std::string internal_ctxid{"INTM"};
const std::string mapped_internal_ctxid{"XXXX"};

Slog2VerbosityLevel toVerbosityLevel(LogLevel loglevel)
{
    Slog2VerbosityLevel verbosity_level = Slog2VerbosityLevel::kShutdown;
    switch (loglevel)
    {
        case LogLevel::kFatal:
            verbosity_level = Slog2VerbosityLevel::kCritical;
            break;
        case LogLevel::kError:
            verbosity_level = Slog2VerbosityLevel::kError;
            break;
        case LogLevel::kWarn:
            verbosity_level = Slog2VerbosityLevel::kWarning;
            break;
        case LogLevel::kInfo:
            verbosity_level = Slog2VerbosityLevel::kInfo;//kNotice
            break;
        case LogLevel::kDebug:
            verbosity_level = Slog2VerbosityLevel::kDebug1;
            break;
        case LogLevel::kVerbose:
            verbosity_level = Slog2VerbosityLevel::kDebug2;
            break;
        default:
            verbosity_level = Slog2VerbosityLevel::kShutdown;
            break;
    }
    return verbosity_level;
}

}  // namespace

LogManager::LogManager()
{
    enable_remote_ = false;
    enable_console_ = false;
    log_contexts_.clear();
}

void LogManager::Initialize(std::string application_id,
		std::string description,
		LogLevel loglevel,
		LogMode logmode,
		std::string directory_path) noexcept
{
     
    if (logmode & LogMode::kConsole) {
	std::cerr << "Unsupport Console Mode" << std::endl; 
        enable_console_ = true;
    }
    
    if (to_integral(logmode) & to_integral(LogMode::kFile)) {
	    std::cerr << "Unsupport File Mode" << std::endl; 
    }
    
    if (to_integral(logmode) & to_integral(LogMode::kRemote)) {
        enable_remote_ = true;
    }
    

    Slog2ReturnValue ret = Slog2RegisterApplication(application_id, description, toVerbosityLevel(loglevel));
    if (ret < Slog2ReturnValue::kReturnOk) {
            std::string err_msg = "logger: unable to register application [" + application_id
            + "] to SLOG2 back-end. Error-code: " + std::to_string(static_cast<int>(ret)).c_str();
            std::cerr<< err_msg << std::endl;
    } else {
        getInternalLogger().LogInfo() << "SLOG2 back-end init finished"
	       	<< Arg(application_id, std::string("appId"))
	       	<< Arg(description, std::string("description"))
		<< Arg(to_integral(logmode), "log mode")
		<< Arg(to_integral(loglevel), "log level");
    }

}

LogManager::~LogManager()
{
    std::lock_guard<std::mutex> guard(log_contexts_mutex_);

    std::for_each(cbegin(log_contexts_), cend(log_contexts_), [](auto&& context) {
		    context.second->deinitialize(); 
    });

    if (internal_logger_) {
        internal_logger_->deinitialize();
    }

    (void)Slog2UnregisterApplication();
}

Logger& LogManager::getInternalLogger() noexcept
{
    std::call_once(init_internal_logger_, [this]() {
        internal_logger_.reset(new Logger(internal_ctxid, "logging API internal context", LogLevel::kVerbose));
	internal_logger_->enableConsole(enable_console_);
	internal_logger_->enableRemote(enable_remote_);
    });
    return *internal_logger_;
}

Logger& LogManager::CreateLogContext(std::string ctxid, std::string description, LogLevel default_loglevel) noexcept
{
    if (ctxid == internal_ctxid) {
        getInternalLogger().LogWarn() << "Context id collision with internally reserved context"
                                      << Arg(ctxid, "contextId") << Arg(mapped_internal_ctxid, "used contextId");
        ctxid = mapped_internal_ctxid;
    }

    try {
        std::lock_guard<std::mutex> guard(log_contexts_mutex_);

        const std::string key{ctxid};
        auto ctx_iter = log_contexts_.find(key);

        if (ctx_iter == log_contexts_.cend()) {
            LoggerPtr ctx_ptr{new Logger(ctxid, description, default_loglevel), Deleter()};
	    ctx_ptr->enableConsole(enable_console_);
	    ctx_ptr->enableRemote(enable_remote_);
            std::tie(ctx_iter, std::ignore) = log_contexts_.emplace(key, std::move(ctx_ptr));
        } else {
            getInternalLogger().LogDebug() << "Context id already registered" << Arg(ctxid, "contextId");
        }

	// Return new Logger
        return *ctx_iter->second.get();

    } catch (const std::system_error& e) {
        getInternalLogger().LogError() << "System error exception" << e.what() << e.code().value()
                                       << Arg(ctxid, "contextId");
    } catch (const std::bad_alloc& e) {
        getInternalLogger().LogError() << "Bad alloc exception" << e.what() << Arg(ctxid, "contextId");
    } catch (...) {
        getInternalLogger().LogError() << "Unexpected exception" << Arg(ctxid, "contextId");
    }

    // We don't want the application to break, so we return the internal context as emergency fallback
    getInternalLogger().LogError() << "Logging context creation failure" << Arg(ctxid, "contextId")
                                   << Arg(internal_ctxid, "emergency fallback contextId");

    return getInternalLogger();
}

void LogManager::Deleter::operator()(Logger* ptr) const
{
    delete ptr;
}

}  // namespace internal
}  // namespace log
}  // namespace nosal
}  // namespace netaos
